home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / jockey / handlers.py < prev    next >
Encoding:
Python Source  |  2009-04-07  |  21.7 KB  |  647 lines

  1. # (c) 2007 Canonical Ltd.
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License along
  14. # with this program; if not, write to the Free Software Foundation, Inc.,
  15. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  16.  
  17. '''Define some common abstract basic handler types.
  18.  
  19. These provide the common functionality for concrete handlers of different
  20. classes, like handlers for a kernel module, a driver package, a handler group,
  21. etc.
  22.  
  23. Custom concrete handlers need to fulfill the following requirements:
  24.  - __init__(self, backend) must take exactly one argument (a reference to a
  25.    Backend instance). All othe properties must be detected by the
  26.    constructor or changed with methods. These classes are instantiated
  27.    automatically, which is not possible with constructors which need more
  28.    arguments.
  29.  
  30.  - All handler types in this module have some abstract functions which need to
  31.    be implemented (see the documentation of the particular classes).
  32. '''
  33.  
  34. import subprocess, os.path, sys, logging
  35. from gettext import gettext as _
  36.  
  37. import detection
  38. from oslib import OSLib
  39.  
  40. #--------------------------------------------------------------------#
  41.  
  42. class Handler:
  43.     '''Abstract basic handler.'''
  44.  
  45.     def __init__(self, backend, name, description=None, rationale=None):
  46.         '''Create a handler with given (human readable) name.
  47.         
  48.         Every handler should have a human readable name. A custom rationale and
  49.         a multi-line description can be given, too. Every handler gets a
  50.         reference to the currently used Backend so that it can request
  51.         installation of packages and other system changes.
  52.  
  53.         By default, available handlers are announced in
  54.         AbstractUI.check(). If you want to have a handler which is
  55.         available, but not announced that way, set self.annonuce to False.
  56.         '''
  57.         self._hwids = [] # covered HardwareIDs
  58.         self._changed = False
  59.         self.backend = backend
  60.  
  61.         self._name = name
  62.         self._description = description
  63.         self._rationale = rationale
  64.         self.license = None
  65.         self.announce = True
  66.  
  67.         # the following properties are not specified in the ctor, since they
  68.         # might be changed after instantiation;
  69.         # subclass ctors might set that before calling us
  70.         if not hasattr(self, '_free'):
  71.             self._free = None
  72.         if hasattr(self, 'package') and self.package:
  73.             self._package_defaults()
  74.         else:
  75.             self.package = None
  76.         self.driver_vendor = None
  77.         self.version = None
  78.         self.repository = None
  79.         self._recommended = False
  80.  
  81.     def _package_defaults(self):
  82.         '''Set fallback name/description/freeness from package.'''
  83.  
  84.         if self.package and (not self._name or self._description is None):
  85.             (distro_name, distro_desc) = OSLib.inst.package_description(self.package)
  86.             if not self._name:
  87.                 self._name = distro_name
  88.             if self._description is None:
  89.                 self._description = distro_desc
  90.         if self.package and self._free is None:
  91.             try:
  92.                 self._free = OSLib.inst.is_package_free(self.package)
  93.             except (KeyError, ValueError):
  94.                 # we cannot determine it right now
  95.                 pass
  96.  
  97.     def name(self):
  98.         '''Return one-line name of the handler (for human consumption).'''
  99.  
  100.         self._package_defaults()
  101.         return self._name
  102.  
  103.     def description(self):
  104.         '''Return multi-line description of the handler.'''
  105.  
  106.         self._package_defaults()
  107.         return self._description
  108.  
  109.     def id(self):
  110.         '''Return an unique identifier of the handler.
  111.  
  112.         This is used for specifying a handler with --enable/--disable on the
  113.         command line, and is mentioned in the --list output.
  114.         '''
  115.         if self.package:
  116.             i = 'pkg:' + self.package
  117.         else:
  118.             i = '%s:%s' % (str(self.__class__).split('.')[-1], self.name())
  119.         if self.driver_vendor:
  120.             i += ':' + self.driver_vendor.replace(' ', '_')
  121.         return i
  122.  
  123.     def rationale(self):
  124.         '''Return rationale as to why this driver might be enabled.
  125.         
  126.         Might return None if no rationale is available.
  127.         '''
  128.         return self._rationale
  129.  
  130.     def changed(self):
  131.         '''Return if the module has been enabled/disabled at least once.'''
  132.  
  133.         return self._changed
  134.  
  135.     #
  136.     # The following methods can be specialized in subclasses
  137.     # 
  138.  
  139.     def can_change(self):
  140.         '''Check whether we can actually modify settings of this handler.
  141.  
  142.         This might not be the case if e. g. the user manually modified a
  143.         configuration file. Return an explanatory text if settings can not be
  144.         changed, or None if changing is ok.
  145.         '''
  146.         return None
  147.  
  148.     def __str__(self):
  149.         return '%s([%s, %s, %s] %s)' % (
  150.             self.id(),
  151.             str(self.__class__).split('.')[-1],
  152.             self.free() and 'free' or 'nonfree',
  153.             self.enabled() and 'enabled' or 'disabled',
  154.             self.name())
  155.  
  156.     #
  157.     # The following methods must be implemented in subclasses
  158.     # 
  159.  
  160.     def free(self):
  161.         '''Return if the handler represents a free software driver.'''
  162.  
  163.         if self._free is not None:
  164.             return self._free
  165.         else:
  166.             raise NotImplementedError, 'subclasses need to implement this'
  167.  
  168.     def enabled(self):
  169.         '''Return if the handler is enabled.
  170.         
  171.         'Enabled' means that the user agreed to use this driver if it is
  172.         applicable.
  173.         '''
  174.         if self.package:
  175.             return OSLib.inst.package_installed(self.package)
  176.         else:
  177.             return True
  178.  
  179.     def used(self):
  180.         '''Return if the handler is currently in use.'''
  181.  
  182.         raise NotImplementedError, 'subclasses need to implement this'
  183.  
  184.     def recommended(self):
  185.         '''Return if the version of a certain driver is recommended over others
  186.         when more than one driver flavour supports the same device.
  187.         
  188.         This method should return True only for the recommended version while
  189.         it will return False for any other compatible version. If only one
  190.         version of a driver is provided, then it should return False.
  191.         '''
  192.         return self._recommended
  193.     
  194.     def available(self):
  195.         '''Return if the conditions to use this handler on the system are met.
  196.  
  197.         This usually means that the hardware for this driver is available, but
  198.         there might be hardware independent drivers, too.
  199.         
  200.         If this returns True or False, the answer is definitive and no further
  201.         detection, db querying, etc is performed. If this returns None, then
  202.         the handler cannot decide availability on its own; in that case it is
  203.         merely available in the handler pool, and an external driver database
  204.         (detection.DriverDB) is queried.
  205.         '''
  206.         if self.package:
  207.             if not self.repository or OSLib.inst.repository_enabled(self.repository):
  208.                 try:
  209.                     OSLib.inst.package_description(self.package)
  210.                     return None
  211.                 except ValueError:
  212.                     return False
  213.             else:
  214.                 return None # undecidable until the repo is added
  215.         else:
  216.             raise NotImplementedError, 'subclasses need to implement this'
  217.  
  218.     def enable(self):
  219.         '''Allow the OS to use it if the hardware is available.
  220.         
  221.         If possible, the handler should be loaded, too. Return True if
  222.         immediately successful, or False if the system needs to be rebooted for
  223.         the changes to become effective.
  224.         '''
  225.         if self.repository:
  226.             OSLib.inst.add_repository(self.repository)
  227.  
  228.         if self.package:
  229.             self.backend.install_package(self.package)
  230.             if not OSLib.inst.package_installed(self.package):
  231.                 # do not touch _changed if package failed to install
  232.                 return True
  233.         self._changed = True
  234.         return True
  235.  
  236.     def disable(self):
  237.         '''Prevent the OS from using it even if the hardware is available.
  238.  
  239.         If possible, the handler should be unloaded, too. Return True if
  240.         immediately successful, or False if the system needs to be rebooted for
  241.         the changes to become effective.
  242.  
  243.         '''
  244.         if self.repository:
  245.             OSLib.inst.remove_repository(self.repository)
  246.  
  247.         if self.package:
  248.             self.backend.remove_package(self.package)
  249.             if OSLib.inst.package_installed(self.package):
  250.                 # do not touch _changed if package failed to remove
  251.                 return
  252.         self._changed = True
  253.  
  254.         return True
  255.  
  256. #--------------------------------------------------------------------#
  257.  
  258. class HandlerGroup(Handler):
  259.     '''Perform operations on a group of handlers.
  260.  
  261.     A group should be provided if it makes little sense to present several very
  262.     similar handlers in the UI. For example, the three VMWare or the dozens of
  263.     commercial OSS drivers should be grouped.
  264.     '''
  265.     def __init__(self, backend, name, id, description=None, rationale=None):
  266.         Handler.__init__(self, backend, name, description, rationale)
  267.         self._id = id
  268.         self.subhandlers = []
  269.  
  270.     def id(self):
  271.         '''Return an unique identifier of the handler.'''
  272.  
  273.         return self._id
  274.  
  275.     def add(self, handler):
  276.         '''Add a subhandler.'''
  277.  
  278.         self.subhandlers.append(handler)
  279.  
  280.     def free(self):
  281.         '''Return if all subhandlers represent free software drivers.'''
  282.  
  283.         for h in self.subhandlers:
  284.             if not h.free():
  285.                 return False
  286.  
  287.         return True
  288.  
  289.     def enabled(self):
  290.         '''Return if all subhandlers are enabled.'''
  291.  
  292.         for h in self.subhandlers:
  293.             if not h.enabled():
  294.                 return False
  295.  
  296.         return True
  297.  
  298.     def used(self):
  299.         '''Return if any subhandler is used.'''
  300.  
  301.         for h in self.subhandlers:
  302.             if h.used():
  303.                 return True
  304.  
  305.         return False
  306.  
  307.     def available(self):
  308.         '''Return if the hardware for any subhandler is available.
  309.         
  310.         If all subhandlers return False, this returns False. If any subhandler
  311.         returns True, this returns True. Otherwise this returns None.
  312.         '''
  313.         all_false = True
  314.  
  315.         for h in self.subhandlers:
  316.             a = h.available()
  317.             if a:
  318.                 return True
  319.             if a == None:
  320.                 all_false = False
  321.             else:
  322.                 assert a == False
  323.  
  324.         if all_false:
  325.             return False
  326.         else:
  327.             return None
  328.  
  329.     def enable(self):
  330.         '''Enable all subhandlers.'''
  331.  
  332.         result = True
  333.         for h in self.subhandlers:
  334.             result = h.enable() and result
  335.         return result
  336.  
  337.     def disable(self):
  338.         '''Disable all subhandlers.'''
  339.  
  340.         result = True
  341.         for h in self.subhandlers:
  342.             result = h.disable() and result
  343.         return result
  344.  
  345.     def changed(self):
  346.         '''Return if at least one subhandler has been enabled/disabled at
  347.         least once.'''
  348.  
  349.         for h in self.subhandlers:
  350.             if h.changed():
  351.                 return True
  352.  
  353.         return False
  354.  
  355.     def can_change(self):
  356.         '''Check whether we can actually modify settings of this handler.'''
  357.  
  358.         assert self.subhandlers
  359.  
  360.         for h in self.subhandlers:
  361.             c = h.can_change()
  362.             if c:
  363.                 return c
  364.  
  365.         return None
  366.  
  367. #--------------------------------------------------------------------#
  368.  
  369. class KernelModuleHandler(Handler):
  370.     '''Handler for a kernel module.
  371.     
  372.     This class can be used as a standard handler for kernel modules (and in
  373.     fact detection.get_handlers() uses this as a default handler if there is no
  374.     custom one). Subclasses have to implement __init__() at least.
  375.     '''
  376.     _loaded_modules = None
  377.     
  378.     def __init__(self, backend, kernel_module, name=None, description=None, rationale=None):
  379.         '''Create handler for a kernel module.
  380.         
  381.         If not given explicitly, the name is read from modinfo's 'description'
  382.         field.
  383.         '''
  384.         self.module = kernel_module
  385.         self._modinfo = detection.get_modinfo(self.module)
  386.         if not name:
  387.             assert self._modinfo, 'kernel module %s exists' % self.module
  388.             name = '\n'.join(self._modinfo.get('description', [self.module]))
  389.         Handler.__init__(self, backend, name, description, rationale)
  390.         self._do_rebind = True
  391.  
  392.     def id(self):
  393.         '''Return an unique identifier of the handler.'''
  394.  
  395.         i = 'kmod:' + self.module
  396.         if self.driver_vendor:
  397.             i += ':' + self.driver_vendor.replace(' ', '_')
  398.         return i
  399.  
  400.     def free(self):
  401.         '''Return if the handler represents a free software driver.'''
  402.  
  403.         # this function needs to be kept in sync with the kernel function
  404.         # is_license_gpl_compatible()
  405.  
  406.         if self._free is not None:
  407.             return self._free
  408.  
  409.         assert self._modinfo, 'kernel module %s exists' % self.module
  410.         for l in self._modinfo.get('license', ['unknown']):
  411.             if l in ('GPL', 'GPL v2', 'GPL and additional rights', 
  412.                 'Dual BSD/GPL', 'Dual MIT/GPL', 'Dual MPL/GPL', 'BSD'):
  413.                 return True
  414.         return False
  415.  
  416.     def enabled(self):
  417.         '''Return if the handler is enabled.
  418.         
  419.         'Enabled' means that the user agreed to use this driver if it is
  420.         applicable.
  421.         '''
  422.         return not OSLib.inst.module_blacklisted(self.module) and \
  423.             (self._modinfo is not None) and Handler.enabled(self)
  424.  
  425.     def used(self):
  426.         '''Return if the handler is currently in use.'''
  427.  
  428.         return self.module_loaded(self.module) and (self.package is None or
  429.             OSLib.inst.package_installed(self.package))
  430.  
  431.     def available(self):
  432.         '''Return if the conditions to use this handler on the system are met
  433.         (e. g. hardware for this driver is available).
  434.  
  435.         This defaults to None, because we usually want to delegate this to the
  436.         driver db. Subclasses are welcome to override this, of course.
  437.         '''
  438.         # check for unavailable package, etc.
  439.         try:
  440.             if Handler.available(self) == False:
  441.                 return False
  442.         except NotImplementedError:
  443.             pass
  444.         return None
  445.  
  446.     def enable(self):
  447.         '''Allow the OS to use it if the hardware is available.
  448.         
  449.         This removes the module from the modprobe blacklist.
  450.         '''
  451.         Handler.enable(self)
  452.         OSLib.inst.blacklist_module(self.module, False)
  453.         subprocess.call([OSLib.inst.modprobe_path, self.module])
  454.         self._modinfo = detection.get_modinfo(self.module)
  455.         self.read_loaded_modules()
  456.         if self._do_rebind:
  457.             return self.rebind(self.module)
  458.  
  459.     def disable(self):
  460.         '''Prevent the OS from using it even if the hardware is available.
  461.  
  462.         This adds the module to the modprobe blacklist.
  463.         '''
  464.         Handler.disable(self)
  465.         OSLib.inst.blacklist_module(self.module, True)
  466.         self._modinfo = detection.get_modinfo(self.module)
  467.         return False # TODO: can we make this automatic?
  468.  
  469.     @classmethod
  470.     def rebind(klass, module):
  471.         '''Re-bind all devices using the module.
  472.         
  473.         This is necessary for example to reload firmware. Return True on
  474.         success, or False if rebind failed for any device.
  475.         '''
  476.         drivers_dir = os.path.join(OSLib.inst.sys_dir, 'module', module, 'drivers')
  477.         if not os.path.isdir(drivers_dir):
  478.             logging.warning('%s does not exist, cannot rebind %s driver' % (
  479.                 drivers_dir, module))
  480.             return
  481.  
  482.         succeeded = True
  483.  
  484.         for driver in os.listdir(drivers_dir):
  485.             driver_path = os.path.join(drivers_dir, driver)
  486.             for device in os.listdir(driver_path):
  487.                 # only consider subdirs which are not called 'module'
  488.                 if device == 'module' or not os.path.isdir(
  489.                     os.path.join(driver_path, device)):
  490.                     continue
  491.                 try:
  492.                     logging.debug('unbind/rebind on driver %s: device %s', driver_path, device)
  493.                     f = open(os.path.join(driver_path, 'unbind'), 'w')
  494.                     f.write(device)
  495.                     f.close()
  496.                     f = open(os.path.join(driver_path, 'bind'), 'w')
  497.                     f.write(device)
  498.                     f.close()
  499.                 except IOError:
  500.                     logging.warning('unbind/rebind for device %s on driver %s failed', 
  501.                         device, driver_path, exc_info=True)
  502.                     succeeded = False
  503.  
  504.         return succeeded
  505.  
  506.     @classmethod
  507.     def read_loaded_modules(klass):
  508.         '''Get the list of loaded kernel modules.'''
  509.  
  510.         klass._loaded_modules = []
  511.  
  512.         proc_modules = open(OSLib.inst.proc_modules)
  513.         try:
  514.             for line in proc_modules:
  515.                 try:
  516.                     line = line[:line.index(' ')]
  517.                 except ValueError:
  518.                     pass
  519.  
  520.                 klass._loaded_modules.append(line.strip())
  521.         finally:
  522.             proc_modules.close()
  523.  
  524.     @classmethod
  525.     def module_loaded(klass, module):
  526.         '''Return if a module is currently loaded.'''
  527.  
  528.         if klass._loaded_modules == None:
  529.             klass.read_loaded_modules()
  530.  
  531.         return module in klass._loaded_modules
  532.  
  533. #--------------------------------------------------------------------#
  534.  
  535. class FirmwareHandler(KernelModuleHandler):
  536.     '''Handler for an already available kernel module needing firmware.
  537.  
  538.     Subclasses need to extend enable() and implement disable() to do something
  539.     with the downloaded file (unpack it, put into the right directory, etc.).
  540.     This class' enable() function will deal with downloading it and the UI
  541.     progress reporting of the download.
  542.     '''
  543.     def __init__(self, backend, kernel_module, testfile, name=None, description=None, 
  544.             rationale=None, url=None, sha1sum=None, free=False):
  545.         '''Create handler for a piece of firmware for a kernel module.
  546.         
  547.         The required argument 'url' specifies where the firmware can be
  548.         downloaded from. The optional 'sha1sum' argument provides a checksum of
  549.         the downloaded file. The file will not be installed if it does not
  550.         match.
  551.  
  552.         enabled() will return True iff the path in testfile exists.
  553.  
  554.         By default this handler assumes that the firmware is not free (since
  555.         otherwise the distribution could ship it together with the driver). You
  556.         can set 'free' to True for free firmware or to None to use the kernel
  557.         module's freeness.
  558.     
  559.         If not given explicitly, the name is read from modinfo's 'description'
  560.         field.
  561.         '''
  562.         self.url = url
  563.         self.sha1sum = sha1sum
  564.         self._free = free
  565.         self.testfile = testfile
  566.  
  567.         KernelModuleHandler.__init__(self, backend, kernel_module, name,
  568.             description, rationale)
  569.  
  570.     def id(self):
  571.         '''Return an unique identifier of the handler.'''
  572.  
  573.         i = 'firmware:' + self.module
  574.         if self.driver_vendor:
  575.             i += ':' + self.driver_vendor.replace(' ', '_')
  576.         return i
  577.  
  578.     def free(self):
  579.         '''Return if the handler represents a free software driver.'''
  580.  
  581.         if self._free is None:
  582.             return KernelModuleHandler.free(self)
  583.         return self._free
  584.  
  585.     def enabled(self):
  586.         '''Return if the handler is enabled.
  587.         
  588.         'Enabled' means that the user agreed to use this driver if it is
  589.         applicable.
  590.         '''
  591.         return os.path.exists(self.testfile) and KernelModuleHandler.enabled(self)
  592.  
  593.     def used(self):
  594.         '''Return if the handler is currently in use.'''
  595.  
  596.         return self.enabled() and KernelModuleHandler.used(self)
  597.  
  598.     def enable(self):
  599.         '''Allow the OS to use it if the hardware is available.
  600.         
  601.         This downloads the url and puts it into self.firmware_file. Subclasses
  602.         need to provide an actual implementation what to do with the file.
  603.         '''
  604.         raise NotImplementedError, 'FirmwareHandler is currently not implemented'
  605.         #self.firmware_file = self.ui.download_url(self.url)[0]
  606.         #if not self.firmware_file:
  607.         #    return
  608.  
  609.         # TODO: sha1sum check
  610.  
  611.         KernelModuleHandler.enable(self)
  612.  
  613.     def disable(self):
  614.         '''Prevent the OS from using it even if the hardware is available.
  615.         
  616.         Implementation in subclasses need to remove the firmware files and call
  617.         KernelModuleHandler.disable().
  618.         '''
  619.         raise NotImplementedError, 'subclasses need to implement this'
  620.  
  621. #--------------------------------------------------------------------#
  622.  
  623. class PrinterDriverHandler(Handler):
  624.     '''Handler for a printer driver.'''
  625.  
  626.     def id(self):
  627.         '''Return an unique identifier of the handler.
  628.  
  629.         This is used for specifying a handler with --enable/--disable on the
  630.         command line, and is mentioned in the --list output.
  631.         '''
  632.         if self.package:
  633.             i = 'printer:' + self.package
  634.         else:
  635.             i = 'printer:%s' % self.name()
  636.         if self.version:
  637.             i += ':' + str(self.version)
  638.         elif self.driver_vendor:
  639.             i += ':' + self.driver_vendor.replace(' ', '_')
  640.         return i
  641.  
  642.     def used(self):
  643.         '''Return if the handler is currently in use.'''
  644.  
  645.         # TODO: query cups for actually using the driver
  646.         return self.enabled()
  647.